package com.freewater.library.lending.reader.application;

import com.freewater.library.common.domain.BaseAggregateRoot;
import com.freewater.library.common.domain.DomainEvent;
import com.freewater.library.common.domain.DomainException;
import com.freewater.library.common.infrastructure.event.DomainEventDispatcher;
import com.freewater.library.lending.reader.model.Reader;
import com.freewater.library.lending.reader.model.ReaderIdGenerator;
import com.freewater.library.lending.reader.model.ReaderRepository;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.Validate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;

@Component
@RequiredArgsConstructor
public class ReaderCommandService {

    private final ReaderRepository readerRepository;
    private final DomainEventDispatcher domainEventDispatcher;
    private final ReaderIdGenerator readerIdGenerator;

    @Transactional
    public void createReader(String readerName) {
        if (this.readerRepository.existsByName(readerName)) {
            throw new DomainException(String.format("已经存在名为[%s]的读者", readerName));
        }
        Reader reader = new Reader(readerIdGenerator.nextId(), readerName);
        this.readerRepository.save(reader);
        this.dispatchEvents(reader);
    }

    @Transactional
    public void enableReader(Long readerId) {
        this.requireReaderId(readerId);
        Reader reader = this.requireReader(readerId);
        reader.enable();
        this.readerRepository.save(reader);
        this.dispatchEvents(reader);
    }

    @Transactional
    public void disableReader(Long readerId) {
        this.requireReaderId(readerId);
        Reader reader = this.requireReader(readerId);
        reader.disable();
        this.readerRepository.save(reader);
        this.dispatchEvents(reader);
    }

    @Transactional
    public void reservedBook(Long readerId, String qrCode) {
        requireReaderId(readerId);
        requireQrCode(qrCode);
        Reader reader = requireReader(readerId);
        reader.reservedBook(qrCode);
        this.readerRepository.save(reader);
        this.dispatchEvents(reader);
    }

    @Transactional
    public void canceledReservedBook(Long readerId, String qrCode) {
        requireReaderId(readerId);
        requireQrCode(qrCode);
        Reader reader = requireReader(readerId);
        reader.cancelReservedBook(qrCode);
        this.readerRepository.save(reader);
        this.dispatchEvents(reader);
    }

    @Transactional
    public void checkedOutBook(Long readerId, String qrCode) {
        requireReaderId(readerId);
        requireQrCode(qrCode);
        Reader reader = requireReader(readerId);
        reader.checkedOutBook(qrCode);
        this.readerRepository.save(reader);
        this.dispatchEvents(reader);
    }

    @Transactional
    public void checkedOutOverdue(Long readerId, String qrCode) {
        requireReaderId(readerId);
        requireQrCode(qrCode);
        Reader reader = requireReader(readerId);
        reader.checkedOutBookOverdue(qrCode);
        this.readerRepository.save(reader);
        this.dispatchEvents(reader);
    }

    @Transactional
    public void returnedBackBook(Long readerId, String qrCode) {
        requireReaderId(readerId);
        requireQrCode(qrCode);
        Reader reader = requireReader(readerId);
        reader.returnedBackBook(qrCode);
        this.readerRepository.save(reader);
        this.dispatchEvents(reader);
    }

    @Transactional
    public void checkedOutBookLost(Long readerId, String qrCode) {
        requireReaderId(readerId);
        requireQrCode(qrCode);
        Reader reader = requireReader(readerId);
        reader.checkedOutBookLost(qrCode);
        this.readerRepository.save(reader);
        this.dispatchEvents(reader);
    }

    private void dispatchEvents(BaseAggregateRoot agr) {
        for (DomainEvent event : agr.getEvents()) {
            this.domainEventDispatcher.dispatch(event);
        }
        agr.clearEvents();
    }

    private Reader requireReader(Long readerId) {
        Optional<Reader> readerOpt = this.readerRepository.findById(readerId);
        Validate.isTrue(readerOpt.isPresent(), "读者不存在");
        return readerOpt.get();
    }

    private void requireQrCode(String qrCode) {
        Validate.notBlank(qrCode, "二维码不能为空");
    }

    private void requireReaderId(Long readerId) {
        Validate.notNull(readerId, "读者Id不能为空");
    }
}
